!(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery"], factory);
  } else if (typeof exports === "object") {
    factory(require("jquery"));
  } else {
    factory(root.jQuery);
  }
})(this, function ($) {
  "use strict";

  /**
   * Name of the plugin
   * @private
   * @const
   * @type {String}
   */
  var PLUGIN_NAME = "vide";

  /**
   * Default settings
   * @private
   * @const
   * @type {Object}
   */
  var DEFAULTS = {
    volume: 1,
    playbackRate: 1,
    muted: true,
    loop: true,
    autoplay: true,
    position: "50% 50%",
    posterType: "detect",
    resizing: true,
    bgColor: "transparent",
    className: "",
  };

  /**
   * Not implemented error message
   * @private
   * @const
   * @type {String}
   */
  var NOT_IMPLEMENTED_MSG = "Not implemented";

  /**
   * Parse a string with options
   * @private
   * @param {String} str
   * @returns {Object|String}
   */
  function parseOptions(str) {
    var obj = {};
    var delimiterIndex;
    var option;
    var prop;
    var val;
    var arr;
    var len;
    var i;

    // Remove spaces around delimiters and split
    arr = str
      .replace(/\s*:\s*/g, ":")
      .replace(/\s*,\s*/g, ",")
      .split(",");

    // Parse a string
    for (i = 0, len = arr.length; i < len; i++) {
      option = arr[i];

      // Ignore urls and a string without colon delimiters
      if (
        option.search(/^(http|https|ftp):\/\//) !== -1 ||
        option.search(":") === -1
      ) {
        break;
      }

      delimiterIndex = option.indexOf(":");
      prop = option.substring(0, delimiterIndex);
      val = option.substring(delimiterIndex + 1);

      // If val is an empty string, make it undefined
      if (!val) {
        val = undefined;
      }

      // Convert a string value if it is like a boolean
      if (typeof val === "string") {
        val = val === "true" || (val === "false" ? false : val);
      }

      // Convert a string value if it is like a number
      if (typeof val === "string") {
        val = !isNaN(val) ? +val : val;
      }

      obj[prop] = val;
    }

    // If nothing is parsed
    if (prop == null && val == null) {
      return str;
    }

    return obj;
  }

  /**
   * Parse a position option
   * @private
   * @param {String} str
   * @returns {Object}
   */
  function parsePosition(str) {
    str = "" + str;

    // Default value is a center
    var args = str.split(/\s+/);
    var x = "50%";
    var y = "50%";
    var len;
    var arg;
    var i;

    for (i = 0, len = args.length; i < len; i++) {
      arg = args[i];

      // Convert values
      if (arg === "left") {
        x = "0%";
      } else if (arg === "right") {
        x = "100%";
      } else if (arg === "top") {
        y = "0%";
      } else if (arg === "bottom") {
        y = "100%";
      } else if (arg === "center") {
        if (i === 0) {
          x = "50%";
        } else {
          y = "50%";
        }
      } else {
        if (i === 0) {
          x = arg;
        } else {
          y = arg;
        }
      }
    }

    return { x: x, y: y };
  }

  /**
   * Search a poster
   * @private
   * @param {String} path
   * @param {Function} callback
   */
  function findPoster(path, callback) {
    var onLoad = function () {
      callback(this.src);
    };

    $('<img src="' + path + '.gif">').on("load", onLoad);
    $('<img src="' + path + '.jpg">').on("load", onLoad);
    $('<img src="' + path + '.jpeg">').on("load", onLoad);
    $('<img src="' + path + '.png">').on("load", onLoad);
  }

  /**
   * Vide constructor
   * @param {HTMLElement} element
   * @param {Object|String} path
   * @param {Object|String} options
   * @constructor
   */
  function Vide(element, path, options) {
    this.$element = $(element);

    // Parse path
    if (typeof path === "string") {
      path = parseOptions(path);
    }

    // Parse options
    if (!options) {
      options = {};
    } else if (typeof options === "string") {
      options = parseOptions(options);
    }

    // Remove an extension
    if (typeof path === "string") {
      path = path.replace(/\.\w*$/, "");
    } else if (typeof path === "object") {
      for (var i in path) {
        if (path.hasOwnProperty(i)) {
          path[i] = path[i].replace(/\.\w*$/, "");
        }
      }
    }

    this.settings = $.extend({}, DEFAULTS, options);
    this.path = path;

    // https://github.com/VodkaBears/Vide/issues/110
    try {
      this.init();
    } catch (e) {
      if (e.message !== NOT_IMPLEMENTED_MSG) {
        throw e;
      }
    }
  }

  /**
   * Initialization
   * @public
   */
  Vide.prototype.init = function () {
    var vide = this;
    var path = vide.path;
    var poster = path;
    var sources = "";
    var $element = vide.$element;
    var settings = vide.settings;
    var position = parsePosition(settings.position);
    var posterType = settings.posterType;
    var $video;
    var $wrapper;

    // Set styles of a video wrapper
    $wrapper = vide.$wrapper = $("<div>")
      .addClass(settings.className)
      .css({
        position: "absolute",
        "z-index": -1,
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        overflow: "hidden",
        "-webkit-background-size": "cover",
        "-moz-background-size": "cover",
        "-o-background-size": "cover",
        "background-size": "cover",
        "background-color": settings.bgColor,
        "background-repeat": "no-repeat",
        "background-position": position.x + " " + position.y,
      });

    // Get a poster path
    if (typeof path === "object") {
      if (path.poster) {
        poster = path.poster;
      } else {
        if (path.mp4) {
          poster = path.mp4;
        } else if (path.webm) {
          poster = path.webm;
        } else if (path.ogv) {
          poster = path.ogv;
        }
      }
    }

    // Set a video poster
    if (posterType === "detect") {
      findPoster(poster, function (url) {
        $wrapper.css("background-image", "url(" + url + ")");
      });
    } else if (posterType !== "none") {
      $wrapper.css(
        "background-image",
        "url(" + poster + "." + posterType + ")"
      );
    }

    // If a parent element has a static position, make it relative
    if ($element.css("position") === "static") {
      $element.css("position", "relative");
    }

    $element.prepend($wrapper);

    if (typeof path === "object") {
      if (path.mp4) {
        sources += '<source src="' + path.mp4 + '.mp4" type="video/mp4">';
      }

      if (path.webm) {
        sources += '<source src="' + path.webm + '.webm" type="video/webm">';
      }

      if (path.ogv) {
        sources += '<source src="' + path.ogv + '.ogv" type="video/ogg">';
      }

      $video = vide.$video = $("<video>" + sources + "</video>");
    } else {
      $video = vide.$video = $(
        "<video>" +
          '<source src="' +
          path +
          '.mp4" type="video/mp4">' +
          '<source src="' +
          path +
          '.webm" type="video/webm">' +
          '<source src="' +
          path +
          '.ogv" type="video/ogg">' +
          "</video>"
      );
    }

    // https://github.com/VodkaBears/Vide/issues/110
    try {
      $video

        // Set video properties
        .prop({
          autoplay: settings.autoplay,
          loop: settings.loop,
          volume: settings.volume,
          muted: settings.muted,
          defaultMuted: settings.muted,
          playbackRate: settings.playbackRate,
          defaultPlaybackRate: settings.playbackRate,
        });
    } catch (e) {
      throw new Error(NOT_IMPLEMENTED_MSG);
    }

    // Video alignment
    $video
      .css({
        margin: "auto",
        position: "absolute",
        "z-index": -1,
        top: position.y,
        left: position.x,
        "-webkit-transform":
          "translate(-" + position.x + ", -" + position.y + ")",
        "-ms-transform": "translate(-" + position.x + ", -" + position.y + ")",
        "-moz-transform": "translate(-" + position.x + ", -" + position.y + ")",
        transform: "translate(-" + position.x + ", -" + position.y + ")",

        // Disable visibility, while loading
        // visibility: 'hidden',
        // opacity: 0
      })

      // Resize a video, when it's loaded
      .one("canplaythrough." + PLUGIN_NAME, function () {
        vide.resize();
      })

      // Make it visible, when it's already playing
      .one("playing." + PLUGIN_NAME, function () {
        $video.css({
          visibility: "visible",
          opacity: 1,
        });
        $wrapper.css("background-image", "none");
      });

    // Resize event is available only for 'window'
    // Use another code solutions to detect DOM elements resizing
    $element.on("resize." + PLUGIN_NAME, function () {
      if (settings.resizing) {
        vide.resize();
      }
    });

    // Append a video
    $wrapper.append($video);
  };

  /**
   * Get a video element
   * @public
   * @returns {HTMLVideoElement}
   */
  Vide.prototype.getVideoObject = function () {
    return this.$video[0];
  };

  /**
   * Resize a video background
   * @public
   */
  Vide.prototype.resize = function () {
    if (!this.$video) {
      return;
    }

    var $wrapper = this.$wrapper;
    var $video = this.$video;
    var video = $video[0];

    // Get a native video size
    var videoHeight = video.videoHeight;
    var videoWidth = video.videoWidth;

    // Get a wrapper size
    var wrapperHeight = $wrapper.height();
    var wrapperWidth = $wrapper.width();

    if (wrapperWidth / videoWidth > wrapperHeight / videoHeight) {
      $video.css({
        // +2 pixels to prevent an empty space after transformation
        width: wrapperWidth + 2,
        height: "auto",
      });
    } else {
      $video.css({
        width: "auto",

        // +2 pixels to prevent an empty space after transformation
        height: wrapperHeight + 2,
      });
    }
  };

  /**
   * Destroy a video background
   * @public
   */
  Vide.prototype.destroy = function () {
    delete $[PLUGIN_NAME].lookup[this.index];
    this.$video && this.$video.off(PLUGIN_NAME);
    this.$element.off(PLUGIN_NAME).removeData(PLUGIN_NAME);
    this.$wrapper.remove();
  };

  /**
   * Special plugin object for instances.
   * @public
   * @type {Object}
   */
  $[PLUGIN_NAME] = {
    lookup: [],
  };

  /**
   * Plugin constructor
   * @param {Object|String} path
   * @param {Object|String} options
   * @returns {JQuery}
   * @constructor
   */
  $.fn[PLUGIN_NAME] = function (path, options) {
    var instance;

    this.each(function () {
      instance = $.data(this, PLUGIN_NAME);

      // Destroy the plugin instance if exists
      instance && instance.destroy();

      // Create the plugin instance
      instance = new Vide(this, path, options);
      instance.index = $[PLUGIN_NAME].lookup.push(instance) - 1;
      $.data(this, PLUGIN_NAME, instance);
    });

    return this;
  };

  $(document).ready(function () {
    var $window = $(window);

    // Window resize event listener
    $window.on("resize." + PLUGIN_NAME, function () {
      for (
        var len = $[PLUGIN_NAME].lookup.length, i = 0, instance;
        i < len;
        i++
      ) {
        instance = $[PLUGIN_NAME].lookup[i];

        if (instance && instance.settings.resizing) {
          instance.resize();
        }
      }
    });

    // https://github.com/VodkaBears/Vide/issues/68
    $window.on("unload." + PLUGIN_NAME, function () {
      return false;
    });

    // Auto initialization
    // Add 'data-vide-bg' attribute with a path to the video without extension
    // Also you can pass options throw the 'data-vide-options' attribute
    // 'data-vide-options' must be like 'muted: false, volume: 0.5'
    $(document)
      .find("[data-" + PLUGIN_NAME + "-bg]")
      .each(function (i, element) {
        var $element = $(element);
        var options = $element.data(PLUGIN_NAME + "-options");
        var path = $element.data(PLUGIN_NAME + "-bg");

        $element[PLUGIN_NAME](path, options);
      });
  });
});
